const hx = require('hbuilderx')
const fs = require('fs-extra')
const theme = require('./theme')
const moment = require('moment')

const { join, dirname, basename } = require('path')
const { constant, views, ViewTypeEnum, connections } = require('./cache')
const { minify } = require('html-minifier')
const { version } = require('../../package.json')

async function loadHtml(pageId) {
    const viewPath = join(constant.viewPath, pageId)
    const viewCachePath = join(constant.cachePath, version, 'src', 'views', pageId)
    const viewCacheFile = join(viewCachePath, 'index.html')
    if (await fs.pathExists(viewCacheFile)) {
        return await fs.readFile(viewCacheFile, {
            encoding: 'utf-8'
        })
    }
    const viewFile = join(viewPath, 'index.html')
    const html = await fs.readFile(viewFile, {
        encoding: 'utf-8'
    })
    const formatHtml = html.replace(/(?:href|src)="((?:\.\.\/)+[^"]+)"/g, (match, p1) => {
        let resourceFile = join(viewPath, p1)
        if (p1.endsWith('.css')) {
            const cssText = fs.readFileSync(resourceFile, {
                encoding: 'utf-8'
            })
            const cssPath = dirname(resourceFile)
            const newCssText = cssText.replace(/((?:\.{1,2}\/)+[^"']+)['"]/g, (match, p1) => {
                const path = join(cssPath, p1)
                return match.replace(p1, path.replace(/\\/g, '/'))
            })
            resourceFile = join(viewCachePath, p1)
            fs.outputFileSync(resourceFile, newCssText, {
                encoding: 'utf-8'
            })
        }
        return match.replace(p1, resourceFile)
    })
    const minHtml = minify(formatHtml, {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true,
        minifyCSS: true,
        minifyJS: true
    })
    fs.outputFileSync(viewCacheFile, minHtml, {
        encoding: 'utf-8'
    })
    return minHtml
}

async function loadOptions(pageId) {
    const optionsPath = join(constant.viewPath, pageId, 'index.options.js')
    if (await fs.pathExists(optionsPath)) {
        return require(optionsPath)
    }
    return () => ({})
}

async function loadPage(pageId, options) {
    const page = {
        pageId,
        options
    }
    page.html = await loadHtml(pageId)
    page.loadOptions = await loadOptions(pageId, page, options)
    page.commandId = 1
    page.commands = new Map()
    page.onFn = function (name, data) {
        return new Promise((resolve, reject) => {
            const id = page.commandId++
            page.commands.set(id, {
                resolve,
                reject
            })
            page.postMessage({
                _id: id,
                command: name,
                data
            })
        })
    }
    page.on = async function (msg) {
        const { id, _id, command, data, hasError } = msg
        if (_id) {
            if (page.commands.has(_id)) {
                const { resolve, reject } = page.commands.get(_id)
                if (hasError) {
                    return reject(new Error(data))
                }
                resolve(data)
            }
        } else if (id) {
            let response
            let stime
            let etime
            let hasError = false
            if (command == 'onDialogClose') {
                page.close()
            } else if (command == 'initTheme') {
                response = theme.getHBuilderXThemeData('siderBar')
            } else if (command == 'toast') {
                switch (data.type) {
                    case 'error':
                        hx.window.showErrorMessage(data.msg)
                        break
                    case 'info':
                        hx.window.showInformationMessage(data.msg)
                        break
                    case 'warning':
                        hx.window.showWarningMessage(data.msg)
                        break
                    default:
                        hx.window.showInformationMessage(data.msg)
                        break
                }
            } else if (command == 'reconnect') {
                const conn = connections.get(data).connection
                try {
                    await conn.reconnect()
                } catch (error) {
                    response = error.message
                    hasError = true
                }
            } else if (command == 'disconnect') {
                const conn = connections.get(data)
                conn.disconnect()
            } else if (command == 'onFnScript') {
                const { name, method, args } = data
                const path = join(constant.viewPath, pageId, name, 'index.js')
                try {
                    const module = require(path)
                    response = await module[method](...args)
                } catch (error) {
                    response = error.message
                    hasError = true
                }
            } else {
                try {
                    stime = moment()
                    response = await page.script[command]?.call(page, data)
                } catch (error) {
                    response = error.message
                    hasError = true
                } finally {
                    etime = moment()
                }
            }
            page.postMessage({
                data: response,
                id,
                hasError
            })
        }
    }
    const scriptPath = join(constant.viewPath, pageId, 'index.js')
    if (await fs.pathExists(scriptPath)) {
        page.script = require(scriptPath)
    }
    return page
}

async function openNativeView(pageId, options) {
    const nativeFile = join(constant.viewPath, pageId, 'index-native.js')
    if (await fs.pathExists(nativeFile)) {
        await require(nativeFile)?.(options)
        return true
    }
    return false
}

async function open(pageId, contentId, options) {
    if (await openNativeView(pageId, options)) {
        return
    }
    let pageCache = views[pageId]
    if (pageCache) {
        pageCache.options = options
        if (pageCache.contentId == contentId && !pageCache.isClosed) {
            pageCache.show()
            await pageCache.script.onPageOpen?.call(pageCache, options)
            return
        }
        pageCache.options = options
        pageCache.commands.clear()
        pageCache.commandId = 1
        pageCache.contentId = contentId
        await pageCache.script.onContentChange?.call(pageCache, options)
        pageCache.show()
        return
    }
    const page = await loadPage(pageId, options)
    const { containerId, viewId, viewType, viewOptions } = await page.loadOptions()
    switch (viewType) {
        case ViewTypeEnum.VIEW:
            {
                page.view = hx.window.createWebView(viewId, {
                    enableScripts: true
                })
                page.webView = page.view.webView
                page.show = function () {
                    hx.window.showView({
                        viewId: viewId,
                        containerId: containerId
                    })
                }
                page.close = function () {
                    page.view.dispose()
                    page.isClosed = true
                }
            }
            break
        case ViewTypeEnum.DIALOG:
            {
                page.view = hx.window.createWebViewDialog(viewOptions, {
                    enableScripts: true
                })
                page.webView = page.view.webView
                page.show = function () {
                    page.view.show()
                }
                page.view.onDialogClosed(async data => {
                    await page.script.onClosed?.call(page, data)
                    delete views[pageId]
                })
                page.onError = error => {
                    page.onFn('onError', error.message)
                }
                page.close = function () {
                    page.view.close()
                    page.isClosed = true
                    return true
                }
            }
            break
        default:
            throw new Error('不支持的视图类型' + viewType)
    }
    page.webView.html = page.html
    page.webView.onDidReceiveMessage(page.on)
    page.postMessage = page.webView.postMessage.bind(page.webView)
    page.contentId = contentId
    page.show()
    views[pageId] = page
    return page
}

async function openWithFunction(pageId, contentId, options, fn, ...args) {
    options.defers = [page => page.script[fn].call(page, ...args)]
    await open(pageId, contentId, options)
}

module.exports = {
    open,
    openWithFunction
}
